package com.hang.jvm.risc.util;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileNameUtil;
import com.hang.jvm.risc.CPU;
import com.hang.jvm.risc.exception.ExecuteCommandException;
import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;

/**
 * 编译工具类
 * 需要先安装编译工具，开发环境 Windows11 WSL:Ubuntu-22.04
 * sudo apt install clang
 * sudo apt install lld
 */
@Slf4j
public class CompileCommandUtil {

    /**
     * 编译RISC-V汇编代码，然后执行指令
     *
     * @param cpuType  Cpu类型
     * @param code     汇编代码
     * @param testName 测试名称
     * @param clockNum 时钟数
     * @param <T>      CPU接口
     * @return Cpu对象
     * @throws ExecuteCommandException   编译命令执行异常
     * @throws IOException               读写文件异常
     * @throws InterruptedException
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    public static <T extends CPU> CPU rvCpuHelper(Class<T> cpuType, String code, String testName, int clockNum) throws ExecuteCommandException, IOException, InterruptedException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        // 生成源码文件
        String assemblyFileName = testName.toLowerCase() + ".s";
        File assemblyFile = new File(getAsmPath(), assemblyFileName);
        FileUtil.writeUtf8String(code, assemblyFile);
        // 编译成obj文件
        generateRvObj(assemblyFileName);
        // 生成bin文件
        generateRvBin(testName.toLowerCase() + ".obj");
        // 读取bin文件
        File binFile = new File(getAsmPath(), testName.toLowerCase() + ".bin");
        byte[] codeByteBuf = FileUtil.readBytes(binFile);
        CPU cpu = cpuType.getDeclaredConstructor(byte[].class).newInstance(codeByteBuf);
        for (int i = 0; i < clockNum; i++) {
            long inst = 0;
            // 取指令
            try {
                inst = cpu.fetch();
            } catch (Exception e) {
                log.error("取指令异常: ", e);
                break;
            }
            // 执行指令
            try {
                long nextPc = cpu.execute(inst);
                cpu.setPc(nextPc);
            } catch (Exception e) {
                log.error("执行指令异常：", e);
                break;
            }
        }

        return cpu;

    }

    public static int generateRvObj(String assemblyFileName) throws ExecuteCommandException, IOException, InterruptedException {
        String[] command = {
                "clang",
                "-Wl,-Ttext=0x0",
                "-nostdlib",
                "-march=rv64g",
                "-mabi=lp64",
                "--target=riscv64",
                "-mno-relax",
                "-o",
                FileNameUtil.mainName(assemblyFileName) + ".obj",
                assemblyFileName
        };
        // 执行命令
        return executeCommand(command, getAsmPath());
    }

    public static int generateRvBin(String objFileName) throws ExecuteCommandException, IOException, InterruptedException {
        String[] command = {
                "llvm-objcopy-14",
                "-O",
                "binary",
                objFileName,
                FileNameUtil.mainName(objFileName) + ".bin"
        };
        // 执行命令
        return executeCommand(command, getAsmPath());
    }

    public static int executeCommand(String[] command, File directory) throws IOException, InterruptedException, ExecuteCommandException {
        Process process = new ProcessBuilder(command).directory(directory).start();
        // 读取命令输出
        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        String line;
        while ((line = reader.readLine()) != null) {
            log.info(line);
        }
        // 读取错误流
        BufferedReader readerError = new BufferedReader(new InputStreamReader(process.getErrorStream()));
        String lineError;
        StringBuilder errorStringBuilder = new StringBuilder();
        while ((lineError = readerError.readLine()) != null) {
            errorStringBuilder.append(lineError);
            errorStringBuilder.append('\n');
        }
        // 等待命令执行完成
        int exitCode = process.waitFor();
        log.info("Command exited with code: {}", exitCode);
        // 命令执行不成功，抛出异常
        if (0 != exitCode) {
            throw new ExecuteCommandException(exitCode, errorStringBuilder.toString());
        }
        return exitCode;
    }

    public static File getAsmPath() {
        String currentDirectory = System.getProperty("user.dir");
        return new File(currentDirectory, "asm");
    }

}
